#ifndef __PCU_H
#define __PCU_H

#include "DRAM.h"
#include "Request.h"

#include <vector>
#include <map>
#include <list>
#include <functional>
#include <cassert>
#include <queue>
#include <sstream>
#include <iostream>
#include <regex>
using namespace std;



namespace ramulator
{
    
    template <typename T>
    class Controller;

    template <typename T>
    class PCU
    {
    public:
        Controller<T>* ctrl;

        class Instruction
        {
        public:
            enum class InsType:int
            {
                MOV, MAC, MUL, ADD, MAX
            };
            enum class OpType:int
            {
                GRFA, GRFB, BANK, MAX
            };

            struct Operand
            {
                uint32_t reg_idx;
                uint32_t row_idx;
                uint32_t col_idx;
                string str;
                OpType type;
                Operand()
                {
                    type=OpType::MAX;
                }
                void init(string str_)
                {
                    str=str_;
                    if(str.find("GRFA")!=string::npos)
                        type=OpType::GRFA;
                    else if(str.find("GRFB")!=string::npos)
                        type=OpType::GRFB;
                    else if(str.find("BANK")!=string::npos)
                        type=OpType::BANK;
                    set_idx();
                }
                void set_idx()
                {
                    assert(str!="");
                    regex rx(R"(\d+)"); 
                    smatch matched;
                    string tmp = str;
                    if(type==OpType::BANK)
                    {
                        regex_search(tmp, matched, rx);
                        //cout << "Number found: " << matched[0] << std::endl; // Get Captured Group 1 text
                        row_idx=stoi(matched[0]);
                        tmp = matched.suffix().str();
                        regex_search(tmp, matched, rx);
                        //cout << "Number found: " << matched[0] << std::endl; // Get Captured Group 1 text
                        col_idx=stoi(matched[0]);
                    }
                    else
                    {
                        while (regex_search(tmp, matched, regex(R"(\d+)"))) {
                            //cout << "Number found: " << matched[0] << std::endl; // Get Captured Group 1 text
                            reg_idx=stoi(matched[0]);
                            tmp = matched.suffix().str(); // Proceed to the next match
                        }  
                    }
                }
            };

            InsType type;
            string ins;

            uint32_t exec_cycles;
            uint32_t writeback_cycles;
            uint32_t pc;
            bool exec_completed;
            bool writeback_completed;
            bool load_completed;
            bool no_need_mac;
            Operand src1;
            Operand src2;
            Operand src3;
            Operand dst;
            
            Instruction(string ins_, uint32_t pc_)
            {
                ins=ins_;
                pc=pc_;
                type=InsType::MAX;   
                load_completed=false;
                exec_completed=false;
                no_need_mac=false;
                writeback_completed=false;
            }
            void decode()
            {
                vector<string> ins_vec;
                string result;
                stringstream input(ins);
                while(input>>result)
                    ins_vec.push_back(result);
                //for(int i=0;i<ins_vec.size();i++)
                    //cout<<ins_vec[i]<<endl;
                if(ins_vec[0]=="MOV")
                {
                    writeback_cycles=1;
                    exec_cycles=0;
                    no_need_mac=true;
                    type=InsType::MOV;
                    dst.init(ins_vec[1]);
                    src1.init(ins_vec[2]);
                }
                else if(ins_vec[0]=="MAC")
                {
                    type=InsType::MAC;
                    exec_cycles=5;   
                    writeback_cycles=1;
                    dst.init(ins_vec[1]);
                    src1.init(ins_vec[2]);
                    src2.init(ins_vec[3]);
                    src1.init(ins_vec[1]);
                }
            }
        };

        queue<Instruction> fetch_ins_queue;
        queue<Instruction> load_ins_queue;
        queue<Instruction> exec_ins_queue;
        queue<Instruction> writeback_ins_queue;
        
        uint32_t pc;

        //vector<string> CRF = vector<string>(32, "add");
        string CRF[32];
        float GRFA[8][16];
        float GRFB[8][16];
        float SRFM[8];
        float SRFA[8];
        PCU(Controller<T>* ctrl):ctrl(ctrl)
        {
            pc=0;
    
            CRF[0]="MOV GRFA[4] BANK[2,0]";
            CRF[1]="MOV GRFA[5] BANK[2,1]";
            CRF[2]="MOV GRFA[6] BANK[2,2]";
            CRF[3]="MOV GRFA[7] BANK[2,3]";
            CRF[4]="MAC GRFB[0] BANK[2,4] GRFA[4]";
            CRF[5]="MAC GRFB[0] BANK[2,5] GRFA[5]";
            CRF[6]="MAC GRFB[0] BANK[2,6] GRFA[6]";
            CRF[7]="MAC GRFB[0] BANK[2,7] GRFA[7]";
        }
        void tick(typename T::Command cmd){

            writeback();
            exec();
            load();
            if(int(cmd)==int(T::Command::RD)||int(cmd)==int(T::Command::WR))
                fetch_and_decode();
        }
        bool ended()
        {
            return fetch_ins_queue.empty()&&load_ins_queue.empty()&&
                    exec_ins_queue.empty()&&writeback_ins_queue.empty();
        }
        void fetch_and_decode()
        {
            Instruction ins(CRF[pc], pc);
            ins.decode();
            printf("%5s %10ld:[%d]%s fetched and decoded\n", "", ctrl->clk, ins.pc,ins.ins.c_str());
            fetch_ins_queue.push(ins);
            pc++;
        }
        void load()
        {
            if(!fetch_ins_queue.empty())
            {
                auto& ins=fetch_ins_queue.front();
                if(ins.load_completed)
                {
                    printf("%5s %10ld:[%d]%s load completed\n", "", ctrl->clk,ins.pc, ins.ins.c_str());
                    if(ins.no_need_mac)
                    {
                        
                        writeback_ins_queue.push(fetch_ins_queue.front());
                    }
                    else
                        exec_ins_queue.push(fetch_ins_queue.front());
                    fetch_ins_queue.pop();
                }
            }
        }
        void exec()
        {
            if(!exec_ins_queue.empty())
            {
                auto& ins=exec_ins_queue.front();
                if(!ins.exec_completed)
                {
                    ins.exec_cycles--;
                    if(ins.exec_cycles==0)
                    {
                        printf("%5s %10ld:[%d]%s exec completed\n", "", ctrl->clk,ins.pc, ins.ins.c_str());
                        ins.exec_completed=true;
                        writeback_ins_queue.push(exec_ins_queue.front());
                        exec_ins_queue.pop();
                    }
                }
            }
        }
        void writeback()
        {
            if(!writeback_ins_queue.empty())
            {
                
                auto& ins=writeback_ins_queue.front();
                writeback_ins_queue.front().writeback_cycles--;
                if(ins.writeback_cycles==0)
                {
                    printf("%5s %10ld:[%d]%s writeback completed\n", "", ctrl->clk,ins.pc,ins.ins.c_str());
                    writeback_ins_queue.pop();
                }
                
            } 
        }
    };
}
#endif